package tr.edu.metu.is550.isell.rest.resource;


import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import tr.edu.metu.is550.isell.domain.*;
import tr.edu.metu.is550.isell.repository.UserRepository;
import tr.edu.metu.is550.isell.rest.message.*;
import tr.edu.metu.is550.isell.service.PropertyService;

import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

@Component
@Path("property")
public class PropertyResource {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PropertyService propertyService;

    @Autowired
    Validator validator;


    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("{id}/comment")
    @PreAuthorize("hasRole('ROLE_USER')")
    public Response addComment(@Context UriInfo uriInfo, @PathParam("id") Long id, @Valid CommentMessage message) {
        UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Message createdMessage = propertyService.addComment(id, user.getUsername(), message.getComment());
        return Response.created(uriInfo.getAbsolutePathBuilder().path(String.valueOf(createdMessage.getId())).build()).build();
    }

    @GET
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("{id}/comment")
    @PreAuthorize("hasRole('ROLE_USER')")
    public Response getComments(@PathParam("id") Long id) {
        UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Property property = propertyService.getProperty(id);
        return Response.ok().entity(buildGetCommentsMessage(property)).build();
    }


    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @PreAuthorize("hasRole('ROLE_USER')")
    public Response addProperty(@Context UriInfo uriInfo, @Valid AddPropertyMessage addPropertyMessage) {
        UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Property property = buildPropertyFromAddPropertyMessage(user.getUsername(), addPropertyMessage);
        Property savedProperty = propertyService.addProperty(property);
        return Response.created(uriInfo.getAbsolutePathBuilder().path(String.valueOf(savedProperty.getId())).build()).build();
    }

    @PUT
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @PreAuthorize("hasRole('ROLE_USER')")
    public Response updateProperty(@PathParam("id") Long propertyId, @Valid UpdatePropertyMessage updatePropertyMessage) {
        UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Property newProperty = buildPropertyFromUpdatePropertyMessage(propertyId, updatePropertyMessage);
        propertyService.updateProperty(user.getUsername(), newProperty);
        return Response.ok().build();
    }


    private final static String GOOGLE_STATIC_MAPS_URL = "http://maps.googleapis.com/maps/api/staticmap";

    @GET
    @Path("{id}/map")
    @Produces("image/jpeg")
    @PreAuthorize("hasRole('ROLE_USER')")
    public Response getMap(@PathParam("id") String id) {

        Property property = propertyService.getProperty(Long.valueOf(id));

        try {
            Client c = Client.create();
            WebResource r = c.resource(GOOGLE_STATIC_MAPS_URL);
            byte[] bytes =
                    r.queryParam("zoom", "16")
                            .queryParam("size", "200x200")
                            .queryParam("markers", "color:blue|label:S|" + property.getLocation().getLatitude() + "," + property.getLocation().getLongitude())
                            .queryParam("format", "jpg")
                            .queryParam("sensor", "false")
                            .get(byte[].class);
            return Response.ok(bytes).build();
        } catch (Exception e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).type(MediaType.TEXT_HTML).build();
        }

    }


    @GET
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @PreAuthorize("hasRole('ROLE_USER')")
    public Response getProperty(@PathParam("id") Long propertyId) {
        Property getProperty = propertyService.getProperty(propertyId);
        GetPropertyDetailsResponseMessage response = buildResponseMessageFromProperty(getProperty);
        return Response.ok().entity(response).build();
    }

    @GET
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public SearchPropertyResultMessageWrapper searchProperty(
            @QueryParam("radius") Double radius,
            @QueryParam("latitude") Double latitude,
            @QueryParam("longitude") Double longitude,
            @QueryParam("minPrice") Double minPrice,
            @QueryParam("maxPrice") Double maxPrice,
            @QueryParam("minNumOfRooms") Integer minNumOfRooms,
            @QueryParam("maxNumOfRooms") Integer maxNumOfRooms,
            @QueryParam("adType") Integer adType,
            @QueryParam("propertyType") Integer propertyType
    ) {
        SearchPropertyMessage searchPropertyMessage = new SearchPropertyMessage();
        searchPropertyMessage.setRadius(radius);
        searchPropertyMessage.setLatitude(latitude);
        searchPropertyMessage.setLogitude(longitude);
        searchPropertyMessage.setMinPrice(minPrice);
        searchPropertyMessage.setMaxPrice(maxPrice);
        searchPropertyMessage.setMinNumOfRooms(minNumOfRooms);
        searchPropertyMessage.setMaxNumOfRooms(maxNumOfRooms);
        searchPropertyMessage.setAdType(adType);
        searchPropertyMessage.setPropertyType(propertyType);

        validator.validate(searchPropertyMessage);

        Set<ConstraintViolation<SearchPropertyMessage>> violations = validator.validate(searchPropertyMessage);

        if (!violations.isEmpty()) {
            ErrorMessages errorMessages = buildErrorMessagesEntity(violations);
            throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity(errorMessages).build());
        }

        List<SearchPropertyResultMessage> properties = propertyService.searchProperty(searchPropertyMessage);
        return new SearchPropertyResultMessageWrapper(properties);
    }


    private ErrorMessages buildErrorMessagesEntity(Set<ConstraintViolation<SearchPropertyMessage>> violations) {
        Iterator<ConstraintViolation<SearchPropertyMessage>> iterator = violations.iterator();
        ErrorMessages errorMessages = new ErrorMessages();
        while (iterator.hasNext()) {
            ConstraintViolation<?> next = iterator.next();
            errorMessages.add(ErrorCodes.INVALID_INPUT, next.getMessage(), next.getPropertyPath().toString());
        }
        return errorMessages;
    }


    private GetPropertyDetailsResponseMessage buildResponseMessageFromProperty(Property property) {

        GetPropertyDetailsResponseMessage responseMessage = new GetPropertyDetailsResponseMessage();
        responseMessage.setTitle(property.getTitle());
        responseMessage.setAddress(property.getAddress());
        responseMessage.setAdtype(property.getAdtype().getValue());
        responseMessage.setDetails(property.getDetails());
        GeoLocation location = property.getLocation();
        responseMessage.setLatitude(location.getLatitude());
        responseMessage.setLongitude(location.getLongitude());
        responseMessage.setNumberOfRooms(property.getNumberOfRooms());
        responseMessage.setPrice(property.getPrice());
        responseMessage.setPropertyType(property.getPropertyType().getValue());
        responseMessage.setAdvirtiserId(property.getAdvertisedBy().getId());
        responseMessage.setAdvirtiserName(property.getAdvertisedBy().getUserContactInfo().getName());
        responseMessage.setAdvirtiserSurname(property.getAdvertisedBy().getUserContactInfo().getSurname());
        responseMessage.setAdvirtiserEmail(property.getAdvertisedBy().getUserContactInfo().getEmail());
        responseMessage.setAdvirtiserAddress(property.getAdvertisedBy().getUserContactInfo().getAddress());
        responseMessage.setAdvirtiserPhone(property.getAdvertisedBy().getUserContactInfo().getPhone());
        responseMessage.setAdvirtiserLoginName(property.getAdvertisedBy().getUserCredentials().getLoginName());

        return responseMessage;
    }

    private GetCommentsMessageWrapper buildGetCommentsMessage(Property property) {

        GetCommentsMessageWrapper wrapper = new GetCommentsMessageWrapper();


        List<Message> messages = property.getMessages();
        Iterator<Message> messageIterator = messages.iterator();
        CommentMessage message;
        while (messageIterator.hasNext()) {
            message = new CommentMessage();
            Message next = messageIterator.next();
            message.setComment(next.getMessageText());
            message.setUser(next.getMessageOwner().getUserCredentials().getLoginName());
            wrapper.getComments().add(message);
        }

        return wrapper;

    }


    private Property buildPropertyFromAddPropertyMessage(String userLoginName, AddPropertyMessage addPropertyMessage) {

        Property property = new Property();
        property.setAddress(addPropertyMessage.getAddress());
        property.setAdtype(AdType.getByValue(addPropertyMessage.getAdtype()));
        property.setAdvertisedBy(userRepository.findByUserCredentialsLoginName(userLoginName));
        GeoLocation geoLocation = new GeoLocation(addPropertyMessage.getLatitude(), addPropertyMessage.getLongitude());
        property.setLocation(geoLocation);
        property.setTitle(addPropertyMessage.getTitle());
        property.setDetails(addPropertyMessage.getDetails());
        property.setNumberOfRooms(addPropertyMessage.getNumberOfRooms());
        property.setPrice(addPropertyMessage.getPrice());
        property.setPropertyType(PropertyType.getByValue(addPropertyMessage.getPropertyType()));
        return property;
    }

    private Property buildPropertyFromUpdatePropertyMessage(Long id, UpdatePropertyMessage updatePropertyMessage) {
        Property property = new Property();
        property.setId(id);
        property.setAddress(updatePropertyMessage.getAddress());
        property.setAdtype(AdType.getByValue(updatePropertyMessage.getAdtype()));
        GeoLocation geoLocation = new GeoLocation(updatePropertyMessage.getLatitude(), updatePropertyMessage.getLongitude());
        property.setLocation(geoLocation);
        property.setTitle(updatePropertyMessage.getTitle());
        property.setDetails(updatePropertyMessage.getDetails());
        property.setNumberOfRooms(updatePropertyMessage.getNumberOfRooms());
        property.setPrice(updatePropertyMessage.getPrice());
        property.setPropertyType(PropertyType.getByValue(updatePropertyMessage.getPropertyType()));
        return property;
    }

}
